package locutor.draft.r

object RewriterTests extends Rewriter {
  import AST._
    
  def testEvaluation () {
    val eval =
      rule {
        case Add (Num (i), Num (j)) => Num (i + j)
        case Sub (Num (i), Num (j)) => Num (i - j)
        case Mul (Num (i), Num (j)) => Num (i * j)
        case Div (Num (i), Num (0)) => Num (0)  // Hack
        case Div (Num (i), Num (j)) => Num (i / j)
        case Var (_)                => Num (3)  // Hack
      }
    val expA = Add(Num(1), Num(1))
    var resA = everywherebu(eval)(expA)
    println(resA)
    
    resA = reduce(eval)(expA)
    println(resA)
    // ---
    val expB = Add(Num(3), Mul(Num(2), Num(4)))
    var resB = everywherebu(eval)(expB)
    println(resB)
    
    resB = reduce(eval)(expB)
    println(resB)
  }
  
  def main(args : Array[String]) : Unit = {
    testEvaluation
  }
}

object AST {
  type Idn = String

  trait PrettyPrintable {
    def pretty (o : StringBuilder)
  }

  abstract class Exp extends PrettyPrintable with Product {
	  def value : Double
	  def vars : Set[Idn] = Set ()
	  def divsbyzero : Int = 0
	  def depth : Int = 0
	  def intadds : Int = 0
  }

  case class Num (d : Double) extends Exp {
    override def value = d
    override def depth = 2
    def pretty (o : StringBuilder) = o.append (d)
  }

  case class Var (s : Idn) extends Exp {
    // Hack to make tests more interesting
    override def value = 3
    override def vars = Set (s)
    override def depth = 2
    override def toString = "Var(\"" + s + "\")"
    def pretty (o : StringBuilder) = o.append (s)
  }

  case class Neg (e : Exp) extends Exp {
    override def value = - e.value
    override def vars = e.vars
    override def divsbyzero = e.divsbyzero
    override def depth = 1 + e.depth
    override def intadds = e.intadds
    def pretty (o : StringBuilder) = { o.append("(-"); e.pretty(o); o.append (')') }
  }

  abstract class Binary (l : Exp, r : Exp) extends Exp {
    override def vars = l.vars ++ r.vars
    override def divsbyzero = l.divsbyzero + r.divsbyzero
    override def depth = 1 + (l.depth).max (r.depth)
    override def intadds = l.intadds + r.intadds
  }

  case class Add (l : Exp, r : Exp) extends Binary (l, r) {
    override def value = l.value + r.value
    override def intadds = (l, r) match {
      case (Num (_), Num (_)) => 1
      case _                  => super.intadds
    }
    def pretty (o : StringBuilder) = { o.append ('('); l.pretty (o); o.append (" + "); r.pretty (o); o.append (')') }
  }

  case class Sub (l : Exp, r : Exp) extends Binary (l, r) {
    override def value = l.value - r.value
    def pretty (o : StringBuilder) = { o.append ('('); l.pretty (o); o.append (" - "); r.pretty (o); o.append (')') }
  }

  case class Mul (l : Exp, r : Exp) extends Binary (l, r) {
    override def value = l.value * r.value
    def pretty (o : StringBuilder) = { o.append ('('); l.pretty (o); o.append (" * "); r.pretty (o); o.append (')') }
  }

  case class Div (l : Exp, r : Exp) extends Binary (l, r) {
    // Hack: no errors, so return zero for divide by zero
    override def value = if (r.value == 0) 0 else l.value / r.value
    override def divsbyzero = l.divsbyzero + (r match {
      case Num (0) => 1
      case _       => r.divsbyzero
    })
    def pretty (o : StringBuilder) = { o.append ('('); l.pretty (o); o.append (" / "); r.pretty (o); o.append (')') }
  }

  abstract class Stmt extends PrettyPrintable with Product {
    def vars : Set[Idn] = Set ()
  }

  case class Null () extends Stmt {
    def pretty (o : StringBuilder) = o.append (";\n")
  }

  case class Seqn (ss : Seq[Stmt]) extends Stmt {
    override def vars = Set (ss flatMap (_ vars) : _*)
    def pretty (o : StringBuilder) = { o.append ("{\n"); ss.foreach (_.pretty (o)); o.append ("}\n") }
  }

  case class Asgn (s : Idn, e : Exp) extends Stmt {
    override def vars = Set (s)
    override def toString = "Asgn(\"" + s + "\"," + e + ")"
    def pretty (o : StringBuilder) = { o.append (s); o.append (" = "); e.pretty (o); o.append (";\n") }
  }

  case class While (e : Exp, b : Stmt) extends Stmt {
    override def vars = e.vars ++ b.vars
    def pretty (o : StringBuilder) = {
      o.append ("while ("); e.pretty (o); o.append (")\n");
      b.pretty (o);
    }
  }
}

trait PrettyPrinter {
  import AST._
  
  def pretty[T <: PrettyPrintable] (t : T) : String = {
    val buffer = new StringBuilder
    t.pretty (buffer)
    buffer.toString
  }
}
